/*
 * Sylvain BERTRAND <digital.ragnarok@gmail.com>
 * code protected by GNU GPL v3
 */
#include <X11/Xlib.h>
#include <X11/extensions/sync.h>
#include <glib.h>
#include <libxml/xinclude.h>
#include "../nls.h"
#include "../toolkit/xml.h"
#include "../toolkit/keyboard.h"
#include "../toolkit/paths.h"
#include "window.h"
#include "geom.h"
#include "stacking.h"
#include "misc.h"
#include "client.h"
#include "config.h"
#include "screen.h"
#include "frame.h"
#include "actions.h"
#include "keyboard.h"
#include "translate.h"
#include "mouse.h"
#include "place.h"
#include "wm.h"
#include "moveresize.h"
struct def_key_bind {
	const gchar *key;
	const gchar *actname;
};
struct def_mouse_bind {

	const gchar *button;
	const gchar *context;
	const enum wm_mouse_action mact;
	const gchar *actname;
};
gboolean config_focus_new;
gboolean config_focus_follow;
guint config_focus_delay;
gboolean config_focus_raise;
gboolean config_focus_last;
gboolean config_focus_under_mouse;
gboolean config_unfocus_leave;
enum wm_place_policy config_place_policy;
gboolean config_place_center;
enum wm_place_monitor config_place_monitor;
guint config_primary_monitor_index;
enum wm_place_monitor config_primary_monitor;
gchar *config_theme;
guint config_desktops_num;
guint config_screen_firstdesk;
gboolean config_resize_redraw;
guint config_keyboard_reset_keycode;
guint config_keyboard_reset_state;
gint config_mouse_threshold;
gint config_mouse_dclicktime;
gint config_mouse_screenedgetime;
gboolean config_mouse_screenedgewarp;
gint config_resist_win;
gint config_resist_edge;
GSList *config_per_app_settings;
struct wm_app_settings *config_create_app_settings(void)
{
	struct wm_app_settings *settings = g_slice_new0(struct wm_app_settings);
	settings->type = -1;
	settings->monitor = -1;
	settings->focus = -1;
	settings->desktop = 0;
	settings->layer = -2;
	settings->skip_pager = -1;
	settings->skip_taskbar = -1;
	settings->fullscreen = -1;
	settings->max_horz = -1;
	settings->max_vert = -1;
	return settings;
}

#define copy_if(setting, default) if (src->setting != default) dst->setting = src->setting
void config_app_settings_copy_non_defaults(const struct wm_app_settings *src, struct wm_app_settings *dst)
{
	g_assert(src != NULL);
	g_assert(dst != NULL);
	copy_if(type, (enum wm_client_type)-1);
	copy_if(monitor, -1);
	copy_if(focus, -1);
	copy_if(desktop, 0);
	copy_if(layer, -2);
	copy_if(skip_pager, -1);
	copy_if(skip_taskbar, -1);
	copy_if(fullscreen, -1);
	copy_if(max_horz, -1);
	copy_if(max_vert, -1);
	if(src->pos_given) {
		dst->pos_given = TRUE;
		dst->pos_force = src->pos_force;
		dst->position = src->position;
	}
}

void config_parse_relative_number(gchar *s, gint *num, gint *denom)
{
	*num = strtol(s, &s, 10);
	if(*s == '%') {
		*denom = 100;
	} else if(*s == '/') {
		*denom = atoi(s + 1);
	}
}

void config_parse_gravity_coord(xmlNodePtr node, struct wm_gravity_coord *c)
{
	gchar *s = t_xml_node_string(node);
	if(!g_ascii_strcasecmp(s, "center"))
		c->center = TRUE;
	else {
		gchar *ps = s;
		if(s[0] == '-')
			c->opposite = TRUE;
		if(s[0] == '-' || s[0] == '+')
			ps++;
		config_parse_relative_number(ps, &c->pos, &c->denom);
	}
	g_free(s);
}

static void parse_per_app_settings(xmlNodePtr node, gpointer d)
{
	xmlNodePtr app = t_xml_find_node(node->children, "application");
	gchar *name = NULL, *class = NULL, *title = NULL, *type_str = NULL;
	gboolean name_set, class_set, type_set, title_set;
	enum wm_client_type type;
	gboolean x_pos_given;
	(void)d;
	while(app) {
		x_pos_given = FALSE;
		class_set = t_xml_attr_string(app, "class", &class);
		name_set = t_xml_attr_string(app, "name", &name);
		type_set = t_xml_attr_string(app, "type", &type_str);
		title_set = t_xml_attr_string(app, "title", &title);
		if(type_set) {
			if(!g_ascii_strcasecmp(type_str, "normal"))
				type = WM_CLIENT_TYPE_NORMAL;
			else if(!g_ascii_strcasecmp(type_str, "dialog"))
				type = WM_CLIENT_TYPE_DIALOG;
			else if(!g_ascii_strcasecmp(type_str, "desktop"))
				type = WM_CLIENT_TYPE_DESKTOP;
			else
				type_set = FALSE;
		}
		if(class_set || name_set || title_set || type_set) {
			xmlNodePtr n, c;
			struct wm_app_settings *settings = config_create_app_settings();
			if(name_set)
				settings->name = g_pattern_spec_new(name);
			if(class_set)
				settings->class = g_pattern_spec_new(class);
			if(title_set)
				settings->title = g_pattern_spec_new(title);
			if(type_set)
				settings->type = type;
			if((n = t_xml_find_node(app->children, "position"))) {
				if((c = t_xml_find_node(n->children, "x")))
					if(!t_xml_node_contains(c, "default")) {
						config_parse_gravity_coord(c, &settings->position.x);
						x_pos_given = TRUE;
					}
				if(x_pos_given && (c = t_xml_find_node(n->children, "y")))
					if(!t_xml_node_contains(c, "default")) {
						config_parse_gravity_coord(c, &settings->position.y);
						settings->pos_given = TRUE;
					}
				if(settings->pos_given && (c = t_xml_find_node(n->children, "monitor")))
					if(!t_xml_node_contains(c, "default")) {
						gchar *s = t_xml_node_string(c);
						if(!g_ascii_strcasecmp(s, "mouse"))
							settings->monitor = 0;
						else
							settings->monitor = t_xml_node_int(c);
						g_free(s);
					}
				t_xml_attr_bool(n, "force", &settings->pos_force);
			}
			if((n = t_xml_find_node(app->children, "focus")))
				if(!t_xml_node_contains(n, "default"))
					settings->focus = t_xml_node_bool(n);
			if((n = t_xml_find_node(app->children, "desktop"))) {
				if(!t_xml_node_contains(n, "default")) {
					gchar *s = t_xml_node_string(n);
					if(!g_ascii_strcasecmp(s, "all"))
						settings->desktop = DESKTOP_ALL;
					else {
						gint i = t_xml_node_int(n);
						if(i > 0)
							settings->desktop = i;
					}
					g_free(s);
				}
			}
			if((n = t_xml_find_node(app->children, "layer")))
				if(!t_xml_node_contains(n, "default")) {
					gchar *s = t_xml_node_string(n);
					if(!g_ascii_strcasecmp(s, "above"))
						settings->layer = 1;
					else if(!g_ascii_strcasecmp(s, "below"))
						settings->layer = -1;
					else
						settings->layer = 0;
					g_free(s);
				}
			if((n = t_xml_find_node(app->children, "skip_pager")))
				if(!t_xml_node_contains(n, "default"))
					settings->skip_pager = t_xml_node_bool(n);
			if((n = t_xml_find_node(app->children, "skip_taskbar")))
				if(!t_xml_node_contains(n, "default"))
					settings->skip_taskbar = t_xml_node_bool(n);
			if((n = t_xml_find_node(app->children, "fullscreen")))
				if(!t_xml_node_contains(n, "default"))
					settings->fullscreen = t_xml_node_bool(n);
			if((n = t_xml_find_node(app->children, "maximized")))
				if(!t_xml_node_contains(n, "default")) {
					gchar *s = t_xml_node_string(n);
					if(!g_ascii_strcasecmp(s, "horizontal")) {
						settings->max_horz = TRUE;
						settings->max_vert = FALSE;
					} else if(!g_ascii_strcasecmp(s, "vertical")) {
						settings->max_horz = FALSE;
						settings->max_vert = TRUE;
					} else
						settings->max_horz = settings->max_vert = t_xml_node_bool(n);
					g_free(s);
				}
			config_per_app_settings = g_slist_append(config_per_app_settings, (gpointer)settings);
			g_free(name);
			g_free(class);
			g_free(title);
			g_free(type_str);
			name = class = title = type_str = NULL;
		}
		app = t_xml_find_node(app->next, "application");
	}
}

static void parse_key(xmlNodePtr node, GList *keylist)
{
	gchar *keystring, **keys, **key;
	xmlNodePtr n;
	gboolean is_chroot = FALSE;
	if(!t_xml_attr_string(node, "key", &keystring))
		return;
	t_xml_attr_bool(node, "chroot", &is_chroot);
	keys = g_strsplit(keystring, " ", 0);
	for(key = keys; *key; ++key) {
		keylist = g_list_append(keylist, *key);
		if((n = t_xml_find_node(node->children, "keybind"))) {
			while(n) {
				parse_key(n, keylist);
				n = t_xml_find_node(n->next, "keybind");
			}
		} else if((n = t_xml_find_node(node->children, "action"))) {
			while(n) {
				struct wm_actions_act *action;
				action = actions_parse(n);
				if(action)
					keyboard_bind(keylist, action);
				n = t_xml_find_node(n->next, "action");
			}
		}
		if(is_chroot)
			keyboard_chroot(keylist);
		keylist = g_list_delete_link(keylist, g_list_last(keylist));
	}
	g_strfreev(keys);
	g_free(keystring);
}

static void parse_keyboard(xmlNodePtr node, gpointer d)
{
	xmlNodePtr n;
	gchar *key;
	(void)d;
	keyboard_unbind_all();
	if((n = t_xml_find_node(node->children, "chainQuitKey"))) {
		key = t_xml_node_string(n);
		translate_key(key, &config_keyboard_reset_state, &config_keyboard_reset_keycode);
		g_free(key);
	}
	if((n = t_xml_find_node(node->children, "keybind")))
		while(n) {
			parse_key(n, NULL);
			n = t_xml_find_node(n->next, "keybind");
		}
}

static void parse_mouse(xmlNodePtr node, gpointer d)
{
	xmlNodePtr n, nbut, nact;
	gchar *buttonstr;
	gchar *cxstr;
	enum wm_mouse_action mact;
	(void)d;
	mouse_unbind_all();
	node = node->children;
	if((n = t_xml_find_node(node, "dragThreshold")))
		config_mouse_threshold = t_xml_node_int(n);
	if((n = t_xml_find_node(node, "doubleClickTime")))
		config_mouse_dclicktime = t_xml_node_int(n);
	if((n = t_xml_find_node(node, "screenEdgeWarpTime"))) {
		config_mouse_screenedgetime = t_xml_node_int(n);
		if(config_mouse_screenedgetime && config_mouse_screenedgetime < 25)
			config_mouse_screenedgetime = 25;
	}
	if((n = t_xml_find_node(node, "screenEdgeWarpMouse")))
		config_mouse_screenedgewarp = t_xml_node_bool(n);
	n = t_xml_find_node(node, "context");
	while(n) {
		gchar *modcxstr;
		enum wm_frame_context cx;
		if(!t_xml_attr_string(n, "name", &cxstr))
			goto next_n;
		modcxstr = g_strdup(cxstr);
		while(frame_next_context_from_string(modcxstr, &cx)) {
			if(!cx) {
				gchar *s = strchr(modcxstr, ' ');
				if(s) {
					*s = '\0';
					g_message(_("Invalid context \"%s\" in mouse binding"), modcxstr);
					*s = ' ';
				}
				continue;
			}
			nbut = t_xml_find_node(n->children, "mousebind");
			while(nbut) {
				if(!t_xml_attr_string(nbut, "button", &buttonstr))
					goto next_nbut;
				if(t_xml_attr_contains(nbut, "action", "press"))
					mact = WM_MOUSE_ACTION_PRESS;
				else if(t_xml_attr_contains(nbut, "action", "release"))
					mact = WM_MOUSE_ACTION_RELEASE;
				else if(t_xml_attr_contains(nbut, "action", "click"))
					mact = WM_MOUSE_ACTION_CLICK;
				else if(t_xml_attr_contains(nbut, "action", "doubleclick"))
					mact = WM_MOUSE_ACTION_DOUBLE_CLICK;
				else if(t_xml_attr_contains(nbut, "action", "drag"))
					mact = WM_MOUSE_ACTION_MOTION;
				else
					goto next_nbut;
				nact = t_xml_find_node(nbut->children, "action");
				while(nact) {
					struct wm_actions_act *action;
					if((action = actions_parse(nact)))
						mouse_bind(buttonstr, cx, mact, action);
					nact = t_xml_find_node(nact->next, "action");
				}
				g_free(buttonstr);
next_nbut:
				nbut = t_xml_find_node(nbut->next, "mousebind");
			}
		}
		g_free(modcxstr);
		g_free(cxstr);
next_n:
		n = t_xml_find_node(n->next, "context");
	}
}

static void parse_focus(xmlNodePtr node, gpointer d)
{
	xmlNodePtr n;
	(void)d;
	node = node->children;
	if((n = t_xml_find_node(node, "focusNew")))
		config_focus_new = t_xml_node_bool(n);
	if((n = t_xml_find_node(node, "followMouse")))
		config_focus_follow = t_xml_node_bool(n);
	if((n = t_xml_find_node(node, "focusDelay")))
		config_focus_delay = t_xml_node_int(n);
	if((n = t_xml_find_node(node, "raiseOnFocus")))
		config_focus_raise = t_xml_node_bool(n);
	if((n = t_xml_find_node(node, "focusLast")))
		config_focus_last = t_xml_node_bool(n);
	if((n = t_xml_find_node(node, "underMouse")))
		config_focus_under_mouse = t_xml_node_bool(n);
	if((n = t_xml_find_node(node, "unfocusOnLeave")))
		config_unfocus_leave = t_xml_node_bool(n);
}

static void parse_placement(xmlNodePtr node, gpointer d)
{
	xmlNodePtr n;
	(void)d;
	node = node->children;
	if((n = t_xml_find_node(node, "policy")))
		if(t_xml_node_contains(n, "UnderMouse"))
			config_place_policy = WM_PLACE_POLICY_MOUSE;
	if((n = t_xml_find_node(node, "center")))
		config_place_center = t_xml_node_bool(n);
	if((n = t_xml_find_node(node, "monitor"))) {
		if(t_xml_node_contains(n, "active"))
			config_place_monitor = WM_PLACE_MONITOR_ACTIVE;
		else if(t_xml_node_contains(n, "mouse"))
			config_place_monitor = WM_PLACE_MONITOR_MOUSE;
		else if(t_xml_node_contains(n, "any"))
			config_place_monitor = WM_PLACE_MONITOR_ANY;
	}
	if((n = t_xml_find_node(node, "primaryMonitor"))) {
		config_primary_monitor_index = t_xml_node_int(n);
		if(!config_primary_monitor_index) {
			if(t_xml_node_contains(n, "mouse"))
				config_primary_monitor = WM_PLACE_MONITOR_MOUSE;
		}
	}
}

static void parse_desktops(xmlNodePtr node, gpointer d)
{
	xmlNodePtr n;
	(void)d;
	node = node->children;
	if((n = t_xml_find_node(node, "number"))) {
		gint d = t_xml_node_int(n);
		if(d > 0)
			config_desktops_num = (unsigned)d;
	}
	if((n = t_xml_find_node(node, "firstdesk"))) {
		gint d = t_xml_node_int(n);
		if(d > 0)
			config_screen_firstdesk = (unsigned)d;
	}
}

static void parse_resize(xmlNodePtr node, gpointer d)
{
	xmlNodePtr n;
	(void)d;
	node = node->children;
	if((n = t_xml_find_node(node, "drawContents")))
		config_resize_redraw = t_xml_node_bool(n);
}

static void parse_resistance(xmlNodePtr node, gpointer d)
{
	xmlNodePtr n;
	(void)d;
	node = node->children;
	if((n = t_xml_find_node(node, "strength")))
		config_resist_win = t_xml_node_int(n);
	if((n = t_xml_find_node(node, "screen_edge_strength")))
		config_resist_edge = t_xml_node_int(n);
}

static void bind_default_keyboard(void)
{
	struct def_key_bind *it;
	struct def_key_bind binds[] =
		{ {"A-Tab", "NextWindow"}, {"S-A-Tab", "PreviousWindow"}, {"A-F4", "Close"}, {NULL, NULL} };
	for(it = binds; it->key; ++it) {
		GList *l = g_list_append(NULL, g_strdup(it->key));
		keyboard_bind(l, actions_parse_string(it->actname));
	}
}

static void bind_default_mouse(void)
{
	struct def_mouse_bind *it;
	struct def_mouse_bind binds[] =
		{
{"Left", "Client", WM_MOUSE_ACTION_PRESS, "Focus"}, {"Middle", "Client", WM_MOUSE_ACTION_PRESS, "Focus"},
		{"Right", "Client", WM_MOUSE_ACTION_PRESS, "Focus"}, {"Left", "Desktop", WM_MOUSE_ACTION_PRESS, "Focus"},
		{"Middle", "Desktop", WM_MOUSE_ACTION_PRESS, "Focus"}, {"Right", "Desktop", WM_MOUSE_ACTION_PRESS, "Focus"},
		{"Left", "Client", WM_MOUSE_ACTION_CLICK, "Raise"},
		{"A-Left", "Frame", WM_MOUSE_ACTION_MOTION, "Move"},
			{"A-Middle", "Frame", WM_MOUSE_ACTION_MOTION, "Resize"}, {NULL, NULL, 0, NULL} };
	for(it = binds; it->button; ++it)
		mouse_bind(it->button, frame_context_from_string(it->context), it->mact, actions_parse_string(it->actname));
}

void config_startup(struct t_xml_inst *i)
{
	config_focus_new = TRUE;
	config_focus_follow = FALSE;
	config_focus_delay = 0;
	config_focus_raise = FALSE;
	config_focus_last = TRUE;
	config_focus_under_mouse = FALSE;
	config_unfocus_leave = FALSE;
	t_xml_register(i, "focus", parse_focus, NULL);
	config_place_policy = WM_PLACE_POLICY_SMART;
	config_place_center = TRUE;
	config_place_monitor = WM_PLACE_MONITOR_PRIMARY;
	config_primary_monitor_index = 1;
	config_primary_monitor = WM_PLACE_MONITOR_ACTIVE;
	t_xml_register(i, "placement", parse_placement, NULL);
	config_theme = NULL;
	config_desktops_num = 4;
	config_screen_firstdesk = 1;
	t_xml_register(i, "desktops", parse_desktops, NULL);
	config_resize_redraw = TRUE;
	t_xml_register(i, "resize", parse_resize, NULL);
	translate_key("C-g", &config_keyboard_reset_state, &config_keyboard_reset_keycode);
	bind_default_keyboard();
	t_xml_register(i, "keyboard", parse_keyboard, NULL);
	config_mouse_threshold = 8;
	config_mouse_dclicktime = 200;
	config_mouse_screenedgetime = 400;
	config_mouse_screenedgewarp = FALSE;
	bind_default_mouse();
	t_xml_register(i, "mouse", parse_mouse, NULL);
	config_resist_win = 10;
	config_resist_edge = 20;
	t_xml_register(i, "resistance", parse_resistance, NULL);
	config_per_app_settings = NULL;
	t_xml_register(i, "applications", parse_per_app_settings, NULL);
}

void config_shutdown(void)
{
	GSList *it;
	g_free(config_theme);
	for(it = config_per_app_settings; it; it = g_slist_next(it)) {
		struct wm_app_settings *itd = (struct wm_app_settings *)it->data;
		if(itd->name)
			g_pattern_spec_free(itd->name);
		if(itd->title)
			g_pattern_spec_free(itd->title);
		if(itd->class)
			g_pattern_spec_free(itd->class);
		g_slice_free(struct wm_app_settings, it->data);
	}
	g_slist_free(config_per_app_settings);
}
